集合将自己持有的数据存储在堆上。

不同的集合类型有着不同的性能特性与开销。

常见的集合类型:

  • 动态数组:紧密地存储任意多个值;
  • 字符串:字符的集合,例如 String 类型;
  • 哈希映射:将值关联到一个特定的值上,是映射的特殊实现;

动态数组

动态数组类型 Vec<T>,在单个数据结构中存储多个类型相同的值,这些值会彼此相邻地排布在内存中。

动态数组非常适合在需要存储一系列相同类型值的场景中使用。

创建动态数组

let v: Vec<i32> = Vec::new();

以上显式地增加一个类型标记,因为没插入任何值,Rust 无法自动推导出我们想要存储的元素类型。动态数组在实现中使用了泛型。

在实际编码中,只要向动态数组内插入了数据,Rust 便可以在绝大部分情形下推导出你希望存储的元素类型,你只需要在极少数的场景中对类型进行声明。

使用初始值来创建动态数组,Rust 提供了一个宏 (vec!) 来创建

let v = vec![1, 2, 3];

修改数据

可以使用 push 方法向动态数组中添加数据

let mut v = Vec::new();
v.push(5);
v.push(6);
v.push(7);
v.push(8);

读取数据

有两种方法可以引用存储在动态数组中的值:

  • 索引;
  • get() 方法;

示例代码

fn main() {
    let v = vec![1, 2, 3, 4, 5];
    let third: &i32 = &v[2];
    println!("The third element is {third}");

    let four: Option<&i32> = v.get(3);
    match four {
        Some(four) => println!("The four element is {four}"),
        None => println!("There is no four element."),
    }
}

get() 方法会返回一个 Option<&T> 类型,可进一步对它进行匹配操作。

遍历数据

示例代码

let v = vec![100, 32, 57];
for i in &v {
    println!("{i}");
}

遍历的同时修改数据

let mut v = vec![100, 32, 57];
for i in &mut v {
    *i += 50;
}

枚举与动态数组结合

动态数组只能存储同一类型的值,为了能让动态数据能够存储不同类型的值,可与枚举结合来使用

enum SpreadsheetCell {
    Int(i32),
    Float(f64),
    Text(String),
}

let row = vec![
    SpreadsheetCell::Int(3),
    SpreadsheetCell::Float(10.12),
    SpreadsheetCell::Text(String::from("blue")),
];

字符串与哈希映射

说明都在以下代码注释中展示

// 集合类型:
// 1.动态数组
// 2.字符串
// 3.哈希映射
use std::fmt;
use std::collections::HashMap;

// 通过枚举类型使得动态数组能存储多个类型的值
// #[derive(Debug)]
enum SpreadsheetCell {
    Int(i32),
    Float(f64),
    Text(String),
}

// 自己手动为 SpreadsheetCell 实现 Debug trait
impl fmt::Debug for SpreadsheetCell {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            SpreadsheetCell::Int(value) => write!(f, "Int({})", value),
            SpreadsheetCell::Float(value) => write!(f, "Float({})", value),
            SpreadsheetCell::Text(value) => write!(f, "Text({})", value),
        }
    }
}

fn main() {
    // ================================================
    // 第一种集合类型:动态数组 Vec<T>
    // ================================================
    // 创建动态数组 - 紧密地存储任意多个值
    // 动态数组在实现中使用了泛型
    // v 变量绑定的 Vec<T> 持有 i32 类型的元素
    let mut v1: Vec<i32> = Vec::new();

    // 更新动态数组
    // 在更新动态数组前,需将动态数组声明为可变 mut
    v1.push(5);
    v1.push(6);
    v1.push(7);
    v1.push(8);

    for i in &mut v1 {
        // 通过解引用来获取 i 的值并修改它
        *i += 50;
        println!("{i}");
    }
    
    // Rust 特意提供了一个用于简化代码的 vec! 宏
    // 这个宏可以根据提供的值来创建一个动态数组
    let v2 = vec![1, 2, 3, 4, 5];
    // 通过索引方式来访问动态数组内容
    // third 不可变引用
    let third: &i32 = &v2[2];
    print!("The third element is {third}");
    // 使用 get() 方法来访问动态数组中的元素
    // 好处是当越界访问时不会 panic,而是返回 None
    let four: Option<&i32> = v2.get(3);
    match four {
        Some(four) => println!("The four element is {four}"),
        None => println!("There is no four element."),
    }

    // 通过枚举类型使得动态数组能存储多个类型的值
    let row = vec![
        SpreadsheetCell::Int(3),
        SpreadsheetCell::Float(10.12),
        SpreadsheetCell::Text(String::from("blue")),
    ];
    println!("{:#?}", row);

    // ================================================
    // 第二种集合类型:字符串
    // ================================================
    // 字符串类型;字符串切片 str,常以借用形式 &str 出现
    // 基于 UTF-8 编码
    // 创建新的字符串
    let s1 = String::new();
    // 很多适用于 Vec<T> 的方法同样也适用于 String
    // 因为 String 是基于字节动态数组实现的
    let s2 = "initial contents";
    let s3 = s2.to_string();
    // 以上等价于 let s3 = String::from("initial contents");
    println!("{s1} - {s3}");
    // 更新字符串
    // 使用 +、format! 宏、push_str(字符串切片)、push(单个字符)
    let mut s4 = String::from("foo");
    s4.push_str("bar");
    println!("s4 = {s4}");
    let mut s5 = String::from("lo");
    s5.push('l');
    println!("s5 = {s5}");
    let s6 = String::from("Hello, ");
    let s7 = String::from("World!");
    // + 运算符会调用一个 add 方法
    // 类似 fn add(self, s: &str) -> String,与标准库有些许差别
    // add 函数使用了泛型来定义
    // 只能将 String 类型与 &str 相加,而不能将两个 String 类型的值相加
    // 这里 &s7,即 &String,而不是 &str,编译器会自动将其进行转换,称为解引用转换技术
    // add 不会取得 s 的所有权,所以调用后再去使用 s 是可以的
    let s8 = s6 + &s7; // s6 在此处所有权发生变化
    // s6 不可用
    // println!("s6 = {s6}"); // 报错
    // 而 s7 不会,因为引用
    println!("s8 = {s8}, s7 = {s7}");
    // 拼接多个字符串,内部实现原理可以说是很复杂
    let s9 = String::from("tic");
    let s10 = String::from("tac");
    let s11 = String::from("toe");
    let s12 = s9 + "-" + &s10 + "-" + &s11;
    println!("s12 = {s12}");
    // 针对以上多个字符串合并,Rust 提供了 format! 宏
    // format! 宏会将结果包含在一个 String 中返回
    // 这样更加易读,并且不会夺取任何参数的所有权,因为 format! 生成的代码使用了引用
    let s13 = String::from("tic");
    let s14 = format!("{s13}-{s10}-{s11}");
    println!("s14 = {s14}");
    // 索引字符串,Rust 不像其它语言那样可以通过 [index] 的方式来索引字符串
    // 因为是 UTF-8 编码的
    // 要小心谨慎地使用范围语法来创建字符串切片
    let s15 = "процессоре";
    let s16 = &s15[0..4];
    println!("s16 = {s16}");
    // 遍历字符串的方法
    for c in "цес".chars() {
        println!("{c}");
    }
    for b in "цес".bytes() {
        println!("{b}");
    }

    // ================================================
    // 第三种集合类型:哈希映射 HashMap<K, V>
    // ================================================
    // 内部实现是使用了哈希函数
    // 创建一个哈希映射
    let mut scores = HashMap::new();
    // 如果 Blue 与 Yellow 另外声明的话,例如:let field_name = String::from("Blue")
    // 那么在 insert 后,由于所有权转移到了哈希映射中,field_name 也就失效了
    // 后续再使用 field_name 变量将导致编译发生错误
    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Yellow"), 50);
    // 访问哈希映射中的值
    let team_name = String::from("Blue");
    // get 会返回一个 Option<&V>
    let score = scores.get(&team_name).copied().unwrap_or(0);
    println!("score = {score}");
    // 使用 for 循环遍历哈希映射中所有的键值对
    for (key, value) in &scores {
        println!("{key}: {value}");
    }
    // 更新哈希映射
    // 以下将原来的值给覆盖掉了
    scores.insert(String::from("Blue"), 25);
    println!("{:?}", scores);
    // entry() 检测键是否存在,如果不存在,则将参数作为值插入
    scores.entry(String::from("Yellow")).or_insert(60);
    scores.entry(String::from("Green")).or_insert(80);
    println!("{:?}", scores);
    // 基于旧值来更新值
    let text = "hello world wonderful world";
    let mut map = HashMap::new();
    for word in text.split_whitespace() {
        let count = map.entry(word).or_insert(0);
        *count += 1;
    }
    println!("{:?}", map);
}